home *** CD-ROM | disk | FTP | other *** search
/ Visual Cafe 3 / Visual Cafe 3.ISO / Vcafe / JFC.bin / ParagraphView.java < prev    next >
Text File  |  1998-06-30  |  35KB  |  932 lines

  1. /*
  2.  * @(#)ParagraphView.java    1.45 98/04/09
  3.  * 
  4.  * Copyright (c) 1997 Sun Microsystems, Inc. All Rights Reserved.
  5.  * 
  6.  * This software is the confidential and proprietary information of Sun
  7.  * Microsystems, Inc. ("Confidential Information").  You shall not
  8.  * disclose such Confidential Information and shall use it only in
  9.  * accordance with the terms of the license agreement you entered into
  10.  * with Sun.
  11.  * 
  12.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
  13.  * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  14.  * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  15.  * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
  16.  * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
  17.  * THIS SOFTWARE OR ITS DERIVATIVES.
  18.  * 
  19.  */
  20. package com.sun.java.swing.text;
  21.  
  22. import java.util.Vector;
  23. import java.util.Properties;
  24. import java.awt.*;
  25. import com.sun.java.swing.event.*;
  26.  
  27. /**
  28.  * View of a simple line-wrapping paragraph that supports
  29.  * multiple fonts, colors, components, icons, etc.  It is
  30.  * basically a vertical box with a margin around it.  The 
  31.  * contents of the box are a bunch of rows which are special 
  32.  * horizontal boxes.  This view creates a collection of
  33.  * views that represent the child elements of the paragraph 
  34.  * element.  Each of these views are placed into a row 
  35.  * directly if they will fit, otherwise the <code>breakView</code>
  36.  * method is called to try and carve the view into pieces
  37.  * that fit.
  38.  *
  39.  * @author  Timothy Prinzing
  40.  * @author  Scott Violet
  41.  * @version 1.45 04/09/98
  42.  * @see     View
  43.  */
  44. public class ParagraphView extends BoxView implements TabExpander {
  45.  
  46.     /**
  47.      * Constructs a ParagraphView for the given element.
  48.      *
  49.      * @param elem the element that this view is responsible for
  50.      */
  51.     public ParagraphView(Element elem) {
  52.     super(elem, View.Y_AXIS);
  53.     AttributeSet attr = elem.getAttributes();
  54.     setParagraphInsets(attr);
  55.     justification = StyleConstants.getAlignment(attr);
  56.     layoutSpan = Integer.MAX_VALUE;
  57.     lineSpacing = StyleConstants.getLineSpacing(attr);
  58.     firstLineIndent = (int)StyleConstants.getFirstLineIndent(attr);
  59.     }
  60.  
  61.     /**
  62.      * Loads all of the children to initialize the view.
  63.      * This is called by the <code>setParent</code> method.
  64.      * This is reimplemented to not load any children directly
  65.      * (as they are created in the process of formatting).
  66.      * This does create views to represent the child elements,
  67.      * but they are placed into a pool that is used in the 
  68.      * process of formatting.
  69.      *
  70.      * @param f the view factory
  71.      */
  72.     protected void loadChildren(ViewFactory f) {
  73.         layoutPool = new Vector();
  74.         Element e = getElement();
  75.         int n = e.getElementCount();
  76.         for (int i = 0; i < n; i++) {
  77.             layoutPool.addElement(f.create(e.getElement(i)));
  78.         }
  79.     }
  80.  
  81.     /**
  82.      * Fetches the child view that represents the given position in
  83.      * the model.  This is implemented to walk through the children
  84.      * looking for a range that contains the given position.  In this
  85.      * view the children do not have a one to one mapping with the
  86.      * child elements (i.e. the children are actually rows that
  87.      * represent a portion of the element this view represents).
  88.      *
  89.      * @param pos  the search position >= 0
  90.      * @param a  the allocation to the box on entry, and the
  91.      *   allocation of the view containing the position on exit
  92.      * @returns  the view representing the given position, or 
  93.      *   null if there isn't one
  94.      */
  95.     protected View getViewAtPosition(int pos, Rectangle a) {
  96.         int n = getViewCount();
  97.         for (int i = 0; i < n; i++) {
  98.             View v = getView(i);
  99.             int p0 = v.getStartOffset();
  100.             int p1 = v.getEndOffset();
  101.             if ((pos >= p0) && (pos < p1)) {
  102.                 // it's in this view.
  103.                 childAllocation(i, a);
  104.                 return v;
  105.             }
  106.         }
  107.         return null;
  108.     }
  109.  
  110.     /**
  111.      * Lays out the children.  If the layout span has changed,
  112.      * the rows are rebuilt.  The superclass functionality
  113.      * is called after checking and possibly rebuilding the
  114.      * rows.  If the height has changed, the 
  115.      * <code>preferenceChanged</code> method is called
  116.      * on the parent since the vertical preference is 
  117.      * rigid.
  118.      *
  119.      * @param width  the width to lay out against >= 0.  This is
  120.      *   the width inside of the inset area.
  121.      * @param height the height to lay out against >= 0 (not used
  122.      *   by paragraph, but used by the superclass).  This
  123.      *   is the height inside of the inset area.
  124.      */
  125.     protected void layout(int width, int height) {
  126.         if (layoutSpan != width) {
  127.             int oldHeight = (int) getPreferredSpan(View.Y_AXIS);
  128.             rebuildRows(width);
  129.             int newHeight = (int) getPreferredSpan(View.Y_AXIS);
  130.             if (oldHeight != newHeight) {
  131.                 View p = getParent();
  132.                 p.preferenceChanged(this, false, true);
  133.             }
  134.         }
  135.  
  136.         // do normal box layout
  137.         super.layout(width, height);
  138.     }
  139.  
  140.     /** 
  141.      * Does a a full layout on this View.  This causes all of 
  142.      * the rows (child views) to be rebuilt to match the given 
  143.      * span of the given allocation.
  144.      *
  145.      * @param span  the length to layout against.
  146.      */
  147.     void rebuildRows(int span) {
  148.         layoutSpan = span;
  149.         int p0 = getStartOffset(); 
  150.         int p1 = getEndOffset();
  151.         removeAll();
  152.     boolean firstRow = true;
  153.         while(p0 < p1) {
  154.             int old = p0;
  155.             // PENDING(prinz) The old rows should be reused and
  156.             // new ones created only if needed... and discarded
  157.             // only if not needed.
  158.             Row row = new Row(getElement());
  159.         if(firstRow) {
  160.         // Give it at least 5 pixels.
  161.         row.setInsets((short)0, (short)Math.min(span - 5,
  162.                             firstLineIndent),
  163.                   (short)0, (short)0);
  164.         firstRow = false;
  165.         }
  166.             append(row);
  167.  
  168.             // layout the row to the current span
  169.             layoutRow(row, p0);
  170.             p0 = row.getEndOffset();
  171.             if (p0 <= old) {
  172.                 throw new StateInvariantError("infinite loop in formatting");
  173.             }
  174.         }
  175.     }
  176.  
  177.     /**
  178.      * Creates a row of views that will fit within the 
  179.      * current layout span.  The rows occupy the area
  180.      * from the left inset to the right inset.
  181.      * 
  182.      * @param row the row to fill in with views.  This is assumed
  183.      *   to be empty on entry.
  184.      * @param pos  The current position in the children of
  185.      *   this views element from which to start.  
  186.      */
  187.     void layoutRow(Row row, int pos) {
  188.         int x = tabBase + getLeftInset();
  189.         int spanLeft = layoutSpan;
  190.         int end = getEndOffset();
  191.     // Indentation.
  192.     int preX = x;
  193.     x += row.getLeftInset();
  194.     spanLeft -= (x - preX);
  195.     int availableSpan = spanLeft;
  196.     preX = x;
  197.         while (pos < end  && spanLeft > 0) {
  198.             View v = createView(pos);
  199.             int chunkSpan;
  200.             if (v instanceof TabableView) {
  201.                 chunkSpan = (int) ((TabableView)v).getTabbedSpan(x, this);
  202.             } else {
  203.                 chunkSpan = (int) v.getPreferredSpan(View.X_AXIS);
  204.             }
  205.             spanLeft -= chunkSpan;
  206.             x += chunkSpan;
  207.             row.append(v);
  208.             pos = v.getEndOffset();
  209.         }
  210.         if (spanLeft < 0) {
  211.             // This row is too long and needs to be adjusted.
  212.             adjustRow(row, availableSpan, preX);
  213.         } else if (row.getViewCount() == 0) {
  214.         // Impossible spec... put in whatever is left.
  215.             View v = createView(pos);
  216.         row.append(v);
  217.     }
  218.     // Adjust for line spacing
  219.     if(lineSpacing > 1) {
  220.         int            height = (int)row.getPreferredSpan(View.Y_AXIS);
  221.         int            addition = (int)((float)height * lineSpacing) -
  222.                                    height;
  223.  
  224.         if(addition > 0)
  225.         row.setInsets(row.getTopInset(), row.getLeftInset(),
  226.                   (short)addition, row.getRightInset());
  227.     }
  228.     }
  229.  
  230.     /**
  231.      * Adjusts the given row if possible to fit within the
  232.      * layout span.  By default this will try to find the 
  233.      * highest break weight possible nearest the end of
  234.      * the row.  If a forced break is encountered, the
  235.      * break will be positioned there.
  236.      * 
  237.      * @param r the row to adjust to the current layout
  238.      *  span.
  239.      * @param desiredSpan the current layout span >= 0
  240.      * @param x the location r starts at.
  241.      */
  242.     protected void adjustRow(Row r, int desiredSpan, int x) {
  243.         int n = r.getViewCount();
  244.         int span = 0;
  245.         int bestWeight = BadBreakWeight;
  246.         int bestSpan = 0;
  247.         int bestIndex = -1;
  248.         int bestOffset = 0;
  249.         View v;
  250.         for (int i = 0; i < n; i++) {
  251.             v = r.getView(i);
  252.             int spanLeft = desiredSpan - span;
  253.             int w = v.getBreakWeight(X_AXIS, x + span, spanLeft);
  254.             if (w >= bestWeight) {
  255.                 bestWeight = w;
  256.                 bestIndex = i;
  257.                 bestSpan = span;
  258.                 if (w >= ForcedBreakWeight) {
  259.                     // it's a forced break, so there is
  260.                     // no point in searching further.
  261.                     break;
  262.                 }
  263.             }
  264.             span += v.getPreferredSpan(X_AXIS);
  265.         }
  266.         if (bestIndex < 0) {
  267.             // there is nothing that can be broken, leave
  268.             // it in it's current state.
  269.             return;
  270.         }
  271.  
  272.         // Break the best candidate view, and patch up the row.
  273.         int spanLeft = desiredSpan - bestSpan;
  274.         v = r.getView(bestIndex);
  275.         v = v.breakView(X_AXIS, v.getStartOffset(), x + bestSpan, spanLeft);
  276.         View[] va = new View[1];
  277.         va[0] = v;
  278.         r.replace(bestIndex, n - bestIndex, va);
  279.     }
  280.  
  281.     /**
  282.      * Creates a view that can be used to represent the
  283.      * current chunk.  This is either the entire view from
  284.      * the pool of views being formatted, or it's the 
  285.      * remaining portion of the view
  286.      */
  287.     View createView(int pos) {
  288.         int childIndex = getElement().getElementIndex(pos);
  289.         View v = (View) layoutPool.elementAt(childIndex);
  290.         if (pos == v.getStartOffset()) {
  291.             // return the entire view
  292.             return v;
  293.         }
  294.  
  295.         // return the remaining portion
  296.         v = v.createFragment(pos, v.getEndOffset());
  297.         return v;
  298.     }
  299.  
  300.     // --- TabExpander methods ------------------------------------------
  301.  
  302.     /**
  303.      * Returns the next tab stop position given a reference position.
  304.      * This view implements the tab coordinate system, and calls
  305.      * <code>getTabbedSpan</code> on the logical children in the process 
  306.      * of layout to determine the desired span of the children.  The
  307.      * logical children can delegate their tab expansion upward to
  308.      * the paragraph which knows how to expand tabs. 
  309.      * <code>LabelView</code> is an example of a view that delegates
  310.      * its tab expansion needs upward to the paragraph.
  311.      * <p>
  312.      * This is implemented to try and locate a <code>TabSet</code>
  313.      * in the paragraph element's attribute set.  If one can be
  314.      * found, its settings will be used, otherwise a default expansion
  315.      * will be provided.  The base location for for tab expansion
  316.      * is the left inset from the paragraphs most recent allocation
  317.      * (which is what the layout of the children is based upon).
  318.      *
  319.      * @param x the X reference position
  320.      * @param tabOffset the position within the text stream
  321.      *   that the tab occurred at >= 0.
  322.      * @return the trailing end of the tab expansion >= 0
  323.      * @see TabSet
  324.      * @see TabStop
  325.      * @see LabelView
  326.      */
  327.     public float nextTabStop(float x, int tabOffset) {
  328.     // If the text isn't left justified, offset by 10 pixels!
  329.     if(justification != StyleConstants.ALIGN_LEFT)
  330.             return x + 10.0f;
  331.         x -= tabBase;
  332.         TabSet tabs = getTabSet();
  333.         if(tabs == null) {
  334.             // a tab every 72 pixels.
  335.             return (float)(tabBase + (((int)x / 72 + 1) * 72));
  336.         }
  337.         TabStop tab = tabs.getTabAfter(x + .01f);
  338.         if(tab == null) {
  339.             // no tab, do a default of 5 pixels.
  340.             // Should this cause a wrapping of the line?
  341.             return tabBase + x + 5.0f;
  342.         }
  343.         int alignment = tab.getAlignment();
  344.         int offset;
  345.         switch(alignment) {
  346.         default:
  347.         case TabStop.ALIGN_LEFT:
  348.             // Simple case, left tab.
  349.             return tabBase + tab.getPosition();
  350.         case TabStop.ALIGN_BAR:
  351.             // PENDING: what does this mean?
  352.             return tabBase + tab.getPosition();
  353.         case TabStop.ALIGN_RIGHT:
  354.         case TabStop.ALIGN_CENTER:
  355.             offset = findOffsetToCharactersInString(tabChars,
  356.                                                     tabOffset + 1);
  357.             break;
  358.         case TabStop.ALIGN_DECIMAL:
  359.             offset = findOffsetToCharactersInString(tabDecimalChars,
  360.                                                     tabOffset + 1);
  361.             break;
  362.         }
  363.         if (offset == -1) {
  364.             offset = getEndOffset();
  365.         }
  366.         float charsSize = getPartialSize(tabOffset + 1, offset);
  367.         switch(alignment) {
  368.         case TabStop.ALIGN_RIGHT:
  369.         case TabStop.ALIGN_DECIMAL:
  370.             // right and decimal are treated the same way, the new
  371.             // position will be the location of the tab less the
  372.             // partialSize.
  373.             return tabBase + Math.max(x, tab.getPosition() - charsSize);
  374.         case TabStop.ALIGN_CENTER: 
  375.             // Similar to right, but half the partialSize.
  376.             return tabBase + Math.max(x, tab.getPosition() - charsSize / 2.0f);
  377.         }
  378.         // will never get here!
  379.         return x;
  380.     }
  381.  
  382.     /**
  383.      * Gets the Tabset to be used in calculating tabs.
  384.      *
  385.      * @return the TabSet
  386.      */
  387.     protected TabSet getTabSet() {
  388.     return StyleConstants.getTabSet(getElement().getAttributes());
  389.     }
  390.  
  391.     /**
  392.      * Returns the size used by the views between <code>startOffset</code>
  393.      * and <code>endOffset</code>. This uses getPartialView to calculate the
  394.      * size if the child view implements the TabableView interface. If a 
  395.      * size is needed and a View does not implement the TabableView
  396.      * interface, the preferredSpan will be used.
  397.      *
  398.      * @param startOffset the starting document offset >= 0
  399.      * @param endOffset the ending document offset >= startOffset
  400.      * @return the size >= 0
  401.      */
  402.     protected float getPartialSize(int startOffset, int endOffset) {
  403.         float size = 0.0f;
  404.         int viewIndex;
  405.         int numViews = getViewCount();
  406.         View view;
  407.         int viewEnd;
  408.         int tempEnd;
  409.  
  410.         // Have to search layoutPool!
  411.         // PENDING: when ParagraphView supports breaking location
  412.         // into layoutPool will have to change!
  413.         viewIndex = getElement().getElementIndex(startOffset);
  414.         numViews = layoutPool.size();
  415.         while(startOffset < endOffset && viewIndex < numViews) {
  416.             view = (View) layoutPool.elementAt(viewIndex++);
  417.             viewEnd = view.getEndOffset();
  418.             tempEnd = Math.min(endOffset, viewEnd);
  419.             if(view instanceof TabableView)
  420.                 size += ((TabableView)view).getPartialSpan(startOffset, tempEnd);
  421.             else if(startOffset == view.getStartOffset() &&
  422.                     tempEnd == view.getEndOffset())
  423.                 size += view.getPreferredSpan(View.X_AXIS);
  424.             else
  425.                 // PENDING: should we handle this better?
  426.                 return 0.0f;
  427.             startOffset = viewEnd;
  428.         }
  429.         return size;
  430.     }
  431.  
  432.     /**
  433.      * Finds the next character in the document with a character in
  434.      * <code>string</code>, starting at offset <code>start</code>. If
  435.      * there are no characters found, -1 will be returned.
  436.      *
  437.      * @param string the string of characters
  438.      * @param start where to start in the model >= 0
  439.      * @return the document offset or -1
  440.      */
  441.     protected int findOffsetToCharactersInString(char[] string,
  442.                                                  int start) {
  443.         int stringLength = string.length;
  444.         int end = getEndOffset();
  445.         Segment seg = new Segment();
  446.         try {
  447.             getDocument().getText(start, end - start, seg);
  448.         } catch (BadLocationException ble) {
  449.             return -1;
  450.         }
  451.         for(int counter = seg.offset, maxCounter = seg.offset + seg.count;
  452.             counter < maxCounter; counter++) {
  453.             char currentChar = seg.array[counter];
  454.             for(int subCounter = 0; subCounter < stringLength;
  455.                 subCounter++) {
  456.                 if(currentChar == string[subCounter])
  457.                     return counter - seg.offset + start;
  458.             }
  459.         }
  460.         // No match.
  461.         return -1;
  462.     }
  463.  
  464.     // ---- View methods ----------------------------------------------------
  465.  
  466.     /**
  467.      * Renders using the given rendering surface and area on that
  468.      * surface.  This is implemented to delgate to the superclass
  469.      * after stashing the base coordinate for tab calculations.
  470.      *
  471.      * @param g the rendering surface to use
  472.      * @param a the allocated region to render into
  473.      * @see View#paint
  474.      */
  475.     public void paint(Graphics g, Shape a) {
  476.         Rectangle alloc = a.getBounds();
  477.         tabBase = alloc.x;
  478.         super.paint(g, a);
  479.     }
  480.  
  481.     /**
  482.      * Determines the preferred span for this view along an
  483.      * axis.  For the paragraph it's whatever it was formatted
  484.      * to along the x axis and whatever the box calculation is
  485.      * for the y axis.
  486.      *
  487.      * @param axis may be either View.X_AXIS or View.Y_AXIS
  488.      * @returns  the span the view would like to be rendered into >= 0.
  489.      *           Typically the view is told to render into the span
  490.      *           that is returned, although there is no guarantee.  
  491.      *           The parent may choose to resize or break the view.
  492.      * @exception IllegalArgumentException for an invalid axis
  493.      */
  494.     public float getPreferredSpan(int axis) {
  495.         switch (axis) {
  496.         case View.X_AXIS:
  497.             int cheapSpan = (int) Math.min((long) layoutSpan + 
  498.                                            (long) getLeftInset() + 
  499.                                            (long) getRightInset(),
  500.                                            Integer.MAX_VALUE);
  501.             return (layoutSpan != Integer.MAX_VALUE) ? 
  502.                 cheapSpan : super.getPreferredSpan(axis);
  503.         case View.Y_AXIS:
  504.             return super.getPreferredSpan(axis);
  505.         default:
  506.             throw new IllegalArgumentException("Invalid axis: " + axis);
  507.         }
  508.     }
  509.  
  510.     /**
  511.      * Determines the desired alignment for this view along an
  512.      * axis.  This is implemented to give the alignment to the
  513.      * center of the first row along the y axis, and the default
  514.      * along the x axis.
  515.      *
  516.      * @param axis may be either View.X_AXIS or View.Y_AXIS
  517.      * @returns the desired alignment.  This should be a value
  518.      *   between 0.0 and 1.0 inclusive, where 0 indicates alignment at the
  519.      *   origin and 1.0 indicates alignment to the full span
  520.      *   away from the origin.  An alignment of 0.5 would be the
  521.      *   center of the view.
  522.      */
  523.     public float getAlignment(int axis) {
  524.         switch (axis) {
  525.         case View.Y_AXIS:
  526.         float a = 0.5f;
  527.         if (getViewCount() != 0) {
  528.         int paragraphSpan = (int) getPreferredSpan(View.Y_AXIS);
  529.         View v = getView(0);
  530.         int rowSpan = (int) v.getPreferredSpan(View.Y_AXIS);
  531.         a = (paragraphSpan != 0) ? ((float)(rowSpan / 2)) / paragraphSpan : 0;
  532.         }
  533.             return a;
  534.         default:
  535.             return super.getAlignment(axis);
  536.         }
  537.     }
  538.  
  539.     /**
  540.      * Gets the resize weight.  A value of 0 or less is not resizable.
  541.      *
  542.      * @param axis may be either View.X_AXIS or View.Y_AXIS
  543.      * @return the weight
  544.      * @exception IllegalArgumentException for an invalid axis
  545.      */
  546.     public int getResizeWeight(int axis) {
  547.         switch (axis) {
  548.         case View.X_AXIS:
  549.             return 1;
  550.         case View.Y_AXIS:
  551.             return 0;
  552.         default:
  553.             throw new IllegalArgumentException("Invalid axis: " + axis);
  554.         }
  555.     }
  556.  
  557.     /**
  558.      * Breaks this view on the given axis at the given length.<p>
  559.      * ParagraphView instances are breakable along the Y_AXIS only, and only if
  560.      * <code>len</code> is after the first line.
  561.      *
  562.      * @param axis may be either View.X_AXIS or View.Y_AXIS
  563.      * @param len specifies where a potential break is desired
  564.      *  along the given axis >= 0
  565.      * @param a the current allocation of the view
  566.      * @return the fragment of the view that represents the
  567.      *  given span, if the view can be broken.  If the view
  568.      *  doesn't support breaking behavior, the view itself is
  569.      *  returned.
  570.      * @see View#breakView
  571.      */
  572.     public View breakView(int axis, float len, Shape a) {
  573.         if(axis == View.Y_AXIS) {
  574.             if(a != null) {
  575.                 Rectangle alloc = a.getBounds();
  576.                 setSize(alloc.width, alloc.height);
  577.             }
  578.             // Determine what row to break on.
  579.  
  580.             // PENDING(prinz) add break support
  581.             return this;
  582.         }
  583.         return this;
  584.     }
  585.  
  586.     /**
  587.      * Gets the break weight for a given location.
  588.      * ParagraphView instances are breakable along the Y_AXIS only, and 
  589.      * only if <code>len</code> is after the first row.  If the length
  590.      * is less than one row, a value of BadBreakWeight is returned.
  591.      *
  592.      * @param axis may be either View.X_AXIS or View.Y_AXIS
  593.      * @param len specifies where a potential break is desired >= 0
  594.      * @return a value indicating the attractiveness of breaking here
  595.      * @see View#getBreakWeight
  596.      */
  597.     public int getBreakWeight(int axis, float len) {
  598.         if(axis == View.Y_AXIS) {
  599.             // PENDING(prinz) make this return a reasonable value
  600.             // when paragraph breaking support is re-implemented.
  601.             // If less than one row, bad weight value should be 
  602.             // returned.
  603.             //return GoodBreakWeight;
  604.             return BadBreakWeight;
  605.         }
  606.         return BadBreakWeight;
  607.     }
  608.  
  609.     /**
  610.      * Gives notification that something was inserted into the document
  611.      * in a location that this view is responsible for.
  612.      *
  613.      * @param changes the change information from the associated document
  614.      * @param a the current allocation of the view
  615.      * @param f the factory to use to rebuild if the view has children
  616.      * @see View#insertUpdate
  617.      */
  618.     public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
  619.         // update the pool of logical children
  620.         Element elem = getElement();
  621.         DocumentEvent.ElementChange ec = changes.getChange(elem);
  622.         if (ec != null) {
  623.             // the structure of this element changed.
  624.             updateLogicalChildren(ec, f);
  625.         }
  626.  
  627.         // find and forward if there is anything there to 
  628.         // forward to.  If children were removed then there was
  629.         // a replacement of the removal range and there is no
  630.         // need to forward.
  631.         if (ec == null || (ec.getChildrenRemoved().length == 0)) {
  632.             int pos = changes.getOffset();
  633.             int index = elem.getElementIndex(pos);
  634.             View v = (View) layoutPool.elementAt(index);
  635.             v.insertUpdate(changes, null, f);
  636.         }
  637.  
  638.         // force layout, should do something more intelligent about
  639.         // incurring damage and triggering a new layout.  This is just
  640.         // about as brute force as it can get.
  641.         layoutSpan = Integer.MAX_VALUE;
  642.         preferenceChanged(null, false, true);
  643.         Rectangle alloc = getInsideAllocation(a);
  644.         if (alloc != null) {
  645.             layout((int) alloc.width, (int) alloc.height);
  646.             Component host = getContainer();
  647.             host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
  648.         }
  649.     }
  650.  
  651.     /**
  652.      * Update the logical children to reflect changes made 
  653.      * to the element this view is responsible.  This updates
  654.      * the pool of views used for layout (ie. the views 
  655.      * representing the child elements of the element this
  656.      * view is responsible for).  This is called by the 
  657.      * <code>insertUpdate, removeUpdate, and changeUpdate</code>
  658.      * methods.
  659.      */
  660.     void updateLogicalChildren(DocumentEvent.ElementChange ec, ViewFactory f) {
  661.         int index = ec.getIndex();
  662.         Element[] removedElems = ec.getChildrenRemoved();
  663.         for (int i = 0; i < removedElems.length; i++) {
  664.         View v = (View) layoutPool.elementAt(index);
  665.         v.setParent(null);
  666.             layoutPool.removeElementAt(index);
  667.         }
  668.         Element[] addedElems = ec.getChildrenAdded();
  669.         for (int i = 0; i < addedElems.length; i++) {
  670.             layoutPool.insertElementAt(f.create(addedElems[i]),
  671.                                        index + i);
  672.         }
  673.     }
  674.  
  675.     /**
  676.      * Gives notification that something was removed from the document
  677.      * in a location that this view is responsible for.
  678.      *
  679.      * @param changes the change information from the associated document
  680.      * @param a the current allocation of the view
  681.      * @param f the factory to use to rebuild if the view has children
  682.      * @see View#removeUpdate
  683.      */
  684.     public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
  685.         // update the pool of logical children
  686.         Element elem = getElement();
  687.         DocumentEvent.ElementChange ec = changes.getChange(elem);
  688.         if (ec != null) {
  689.             // the structure of this element changed.
  690.             updateLogicalChildren(ec, f);
  691.         }
  692.  
  693.         // find and forward if there is anything there to 
  694.         // forward to.  If children were added then there was
  695.         // a replacement of the removal range and there is no
  696.         // need to forward.
  697.         if (ec == null || (ec.getChildrenAdded().length == 0)) {
  698.             int pos = changes.getOffset();
  699.             int index = elem.getElementIndex(pos);
  700.             View v = (View) layoutPool.elementAt(index);
  701.             v.removeUpdate(changes, null, f);
  702.         }
  703.  
  704.         // force layout, should do something more intelligent about
  705.         // incurring damage and triggering a new layout.
  706.         layoutSpan = Integer.MAX_VALUE;
  707.         preferenceChanged(null, false, true);
  708.         if (a != null) {
  709.             Rectangle alloc = getInsideAllocation(a);
  710.             layout((int) alloc.width, (int) alloc.height);
  711.             Component host = getContainer();
  712.             host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
  713.         }
  714.     }
  715.  
  716.     /**
  717.      * Gives notification from the document that attributes were changed
  718.      * in a location that this view is responsible for.
  719.      *
  720.      * @param changes the change information from the associated document
  721.      * @param a the current allocation of the view
  722.      * @param f the factory to use to rebuild if the view has children
  723.      * @see View#changedUpdate
  724.      */
  725.     public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
  726.         // update any property settings stored
  727.         AttributeSet attr = getElement().getAttributes();
  728.         setParagraphInsets(attr);
  729.         justification = StyleConstants.getAlignment(attr);
  730.         lineSpacing = StyleConstants.getLineSpacing(attr);
  731.     firstLineIndent = (int)StyleConstants.getFirstLineIndent(attr);
  732.         lineSpacing = (int) StyleConstants.getLineSpacing(attr);
  733.  
  734.         // update the pool of logical children
  735.         Element elem = getElement();
  736.         DocumentEvent.ElementChange ec = changes.getChange(elem);
  737.         if (ec != null) {
  738.             // the structure of this element changed.
  739.             updateLogicalChildren(ec, f);
  740.         }
  741.  
  742.         // forward to the logical children
  743.         int p0 = changes.getOffset();
  744.         int p1 = p0 + changes.getLength();
  745.         int index0 = elem.getElementIndex(p0);
  746.         int index1 = elem.getElementIndex(p1 - 1);
  747.         for (int i = index0; i <= index1; i++) {
  748.             View v = (View) layoutPool.elementAt(i);
  749.             v.changedUpdate(changes, null, f);
  750.         }
  751.  
  752.         // force layout, should do something more intelligent about
  753.         // incurring damage and triggering a new layout.
  754.         layoutSpan = Integer.MAX_VALUE;
  755.         preferenceChanged(null, false, true);
  756.         if (a != null) {
  757.             Rectangle alloc = getInsideAllocation(a);
  758.             layout((int) alloc.width, (int) alloc.height);
  759.             Component host = getContainer();
  760.             host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
  761.         }
  762.     }
  763.  
  764.     // --- variables -----------------------------------------------
  765.  
  766.     private int justification;
  767.     private float lineSpacing;
  768.     /** Indentation for the first line, from the left inset. */
  769.     protected int firstLineIndent;
  770.  
  771.     /**
  772.      * Used by the TabExpander functionality to determine
  773.      * where to base the tab calculations.  This is basically
  774.      * the location of the left side of the paragraph.
  775.      */
  776.     private int tabBase;
  777.  
  778.     /**
  779.      * Used by the layout process.  The span holds the
  780.      * length that has been formatted to. 
  781.      */
  782.     private int layoutSpan;
  783.  
  784.     /**
  785.      * These are the views that represent the child elements
  786.      * of the element this view represents.  These are not
  787.      * directly children of this view.  These are either 
  788.      * placed into the rows directly or used for the purpose
  789.      * of breaking into smaller chunks.
  790.      */
  791.     private Vector layoutPool;
  792.     
  793.     /** Used for searching for a tab. */
  794.     static char[] tabChars;
  795.     /** Used for searching for a tab or decimal character. */
  796.     static char[] tabDecimalChars;
  797.  
  798.     static {
  799.         tabChars = new char[1];
  800.         tabChars[0] = '\t';
  801.         tabDecimalChars = new char[2];
  802.         tabDecimalChars[0] = '\t';
  803.         tabDecimalChars[1] = '.';
  804.     }
  805.  
  806.     /**
  807.      * Internally created view that has the purpose of holding
  808.      * the views that represent the children of the paragraph
  809.      * that have been arranged in rows.
  810.      */
  811.     class Row extends BoxView {
  812.  
  813.         Row(Element elem) {
  814.             super(elem, View.X_AXIS);
  815.         }
  816.  
  817.         /**
  818.          * This is reimplemented to do nothing since the
  819.          * paragraph fills in the row with its needed
  820.          * children.
  821.          */
  822.         protected void loadChildren(ViewFactory f) {
  823.         }
  824.  
  825.     /**
  826.      * Determines the desired alignment for this view along an
  827.      * axis.  This is implemented to give a horizontal alignment
  828.      * appropriate for the kind of justification being done.
  829.      *
  830.      * @param axis may be either View.X_AXIS or View.Y_AXIS
  831.      * @returns the desired alignment >= 0.0f && <= 1.0f.  This should
  832.      *   be a value between 0.0 and 1.0 where 0 indicates alignment at the
  833.      *   origin and 1.0 indicates alignment to the full span
  834.      *   away from the origin.  An alignment of 0.5 would be the
  835.      *   center of the view.
  836.      * @exception IllegalArgumentException for an invalid axis
  837.      */
  838.         public float getAlignment(int axis) {
  839.             if (axis == View.X_AXIS) {
  840.                 switch (justification) {
  841.                 case StyleConstants.ALIGN_LEFT:
  842.                     return 0;
  843.                 case StyleConstants.ALIGN_RIGHT:
  844.                     return 1;
  845.                 case StyleConstants.ALIGN_CENTER:
  846.                 case StyleConstants.ALIGN_JUSTIFIED:
  847.                     return 0.5f;
  848.                 }
  849.             }
  850.             return super.getAlignment(axis);
  851.         }
  852.  
  853.         /**
  854.          * Provides a mapping from the document model coordinate space
  855.          * to the coordinate space of the view mapped to it.  This is
  856.          * implemented to let the superclass find the position along 
  857.          * the major axis and the allocation of the row is used 
  858.          * along the minor axis, so that even though the children 
  859.          * are different heights they all get the same caret height.
  860.          *
  861.          * @param pos the position to convert
  862.          * @param a the allocated region to render into
  863.          * @return the bounding box of the given position
  864.          * @exception BadLocationException  if the given position does not represent a
  865.          *   valid location in the associated document
  866.          * @see View#modelToView
  867.          */
  868.         public Shape modelToView(int pos, Shape a) throws BadLocationException {
  869.             Rectangle r = a.getBounds();
  870.             int height = r.height;
  871.             int y = r.y;
  872.             Shape loc = super.modelToView(pos, a);
  873.             r = loc.getBounds();
  874.             r.height = height;
  875.             r.y = y;
  876.             return r;
  877.         }
  878.  
  879.         /**
  880.          * Range represented by a row in the paragraph is only
  881.          * a subset of the total range of the paragraph element.
  882.          * @see View#getRange
  883.          */
  884.         public int getStartOffset() {
  885.         int offs = Integer.MAX_VALUE;
  886.             int n = getViewCount();
  887.         for (int i = 0; i < n; i++) {
  888.         View v = getView(i);
  889.         offs = Math.min(offs, v.getStartOffset());
  890.         }
  891.             return offs;
  892.         }
  893.  
  894.         public int getEndOffset() {
  895.         int offs = 0;
  896.             int n = getViewCount();
  897.         for (int i = 0; i < n; i++) {
  898.         View v = getView(i);
  899.         offs = Math.max(offs, v.getEndOffset());
  900.         }
  901.             return offs;
  902.         }
  903.  
  904.         /**
  905.          * Fetches the child view that represents the given position in
  906.          * the model.  This is implemented to walk through the children
  907.          * looking for a range that contains the given position.
  908.          * @param pos  The search position 
  909.          * @param a  The allocation to the box on entry, and the
  910.          *   allocation of the view containing the position on exit.
  911.          * @returns  The view representing the given position, or 
  912.          *   null if there isn't one.
  913.          */
  914.         protected View getViewAtPosition(int pos, Rectangle a) {
  915.             int n = getViewCount();
  916.             for (int i = 0; i < n; i++) {
  917.                 View v = getView(i);
  918.                 int p0 = v.getStartOffset();
  919.                 int p1 = v.getEndOffset();
  920.                 if ((pos >= p0) && (pos < p1)) {
  921.                     // it's in this view.
  922.                     this.childAllocation(i, a);
  923.                     return v;
  924.                 }
  925.             }
  926.             return null;
  927.         }
  928.  
  929.     }
  930.  
  931. }
  932.